<!--
 * @Description:  具体使用方式请参考同文件夹下 VueComponent 和 VuePlugIn 文件           
 * @Version: 1.0
 * @Autor: chen_yt
 * @Date: 2020-11-02 17:15:31
 * @LastEditors: chen_yt
 * @LastEditTime: 2022-05-17 15:13:23
-->
<template>
  <div />
</template>
<script>
import Stomp from 'stompjs'
import storage from '@/utils/storage'
import { transRelativeToAbsolute } from './util'

export default {
  name: 'StompConnect',
  props: {
    //是否链接凤翎服务，是的话会在url后拼接一些参数，并改变订阅地址
    isFksServer: {
      type: Boolean,
      default: true
    },
    name: {
      type: String,
      required: true
    },
    //链接地址。支持以相对地址构建，比如../api/server/xxx
    url: {
      type: String,
      default() {
        return this.VUE_APP_SOCKET_API
      }
    },
    //后端控制器的messagingTemplate.convertAndSendToUser中可设置推送地址地址，该地址与前端消息模板中的订阅地址一致
    //e：消息模板中订阅地址为'/topic/message',后端将消息推送到'/topic/message',前端订阅'/topic/message'
    //原理来讲，前端实际订阅的地址是'/user/topic/message'，前面的/user会在isFksServer=true情况下自动添加上
    destination: {
      type: String
    },
    //链接建立方式，现在仅支持stomp及sqmtt
    connectType: {
      type: String,
      default: 'stomp'
    },
    //尝试链接等待5秒，连不上就断开
    connectPendingTime: {
      type: Number,
      default: 5000
    },
    //链接断开后的重连间隔
    reConnectInterval: {
      type: Number,
      default: 1000
    },
    maxReconnect: {
      type: Number,
      default: 15
    },
    clientInPool: {
      type: Object
    }
  },
  data() {
    return {
      //定时器,一段时间连不上就断开
      pendingTimer: null,
      //作为插件时的回调方法
      //重连轮次
      connectRound: -1,
      //是否正在链接
      connectLock: false,
      visibilityState: 'visible',
      stompClient: {}
    }
  },
  computed: {
    currentClient() {
      return this.stompClient
    },
    sendMessage() {
      return this.currentClient.send
    },
    realDestination() {
      return this.isFksServer ? '/user' + this.destination : this.destination
    }
  },
  mounted() {
    this.initConnect()
    window.addEventListener('visibilitychange', this.onVisibilityChange)
  },
  beforeDestroy() {
    this.$emit('closeClient')
    window.removeEventListener('visibilitychange', this.onVisibilityChange)
  },

  methods: {
    /**
     * @description:建立链接的入口，当未给链接命名时拒绝链接
     *
     */
    initConnect() {
      this.connectRound = -1
      this.connectLock = false
      this.stompClient = {}
      this.connection()
    },
    /**
     * @description:建立基于stomp协议的链接，创建的stomp对象会保存到vuex的链接池中
     */
    connection() {
      if (this.connectLock) return
      this.connectLock = true

      //防止并发，当已链接时不再重复链接
      if (this.stompClient.connected) {
        this.connectLock = false
        return
      }

      if (this.stompClient.activeClose) {
        this.emitErrorMsg('链接已被主动关闭')
        this.connectLock = false
        return
      }

      if (this.connectRound >= this.maxReconnect) {
        this.emitErrorMsg('重连已达最大尝试次数')
        this.connectLock = false
        return
      }

      let connectionUrl = this.url

      //如果是凤翎提供的ws服务，加入一些识别参数
      if (this.isFksServer) {
        let accessToken = storage.get('access_token')
        if (!accessToken) {
          this.emitErrorMsg('缺少token')
          this.connectLock = false
          return
        }
        connectionUrl = `${connectionUrl}?fawkes-auth=${accessToken}&onlineClientType=web`
      }

      console.log(
        `try connect round ${this.connectRound}：${this.connectType}-${this.name}`
      )

      this.connectRound++

      // STOMP子协议的客户端对象
      this.stompClient = {}

      //使用原生websocket构建stomp时，要将相对路径转为绝对路径
      connectionUrl = transRelativeToAbsolute(connectionUrl)
      this.stompClient = Stomp.client(connectionUrl)

      //设置stomp基础信息并存入连接池
      this.$set(this.stompClient, 'name', this.name)
      this.$set(this.stompClient, 'activeClose', false)
      this.$set(this.stompClient, 'debug', null)
      this.$set(this.stompClient, 'activeEnd', this.activeCloseMethod)

      if (this.connectRound == 0) {
        this.$emit('createClient')
      } else {
        this.$emit('updateClient')
      }

      // 定义客户端的认证信息,按需求配置
      let headers = {
        Authorization: ''
      }

      //设定一个定时器，规定时间內连不上就断开
      this.pendingTimer = setTimeout(this.disconnect, this.connectPendingTime)

      // 向服务器发起websocket连接
      this.stompClient.connect(
        headers,
        //连接成功回调
        this.onSuccessHandle,
        //连接发生错误回调
        this.onErrorHandle
      )
    },

    clearTimer() {
      if (this.pendingTimer) clearTimeout(this.pendingTimer)
    },

    /**
     * @description: 按照设定好的间隔触发重新连接，
     *               若连接池里没有该名称的链接则说明已被某处关闭，不再链接
     */
    reconnect() {
      if (this.connectLock) return

      if (!this.stompClient.connected) {
        this.disconnect()

        if (!this.clientInPool.name) {
          // this.emitErrorMsg('链接已被删除')
          return
        }
        setTimeout(() => {
          this.connection()
        }, this.reConnectInterval)
      }
    },

    /**
     * @description: 断开链接
     */
    disconnect() {
      this.clearTimer()

      this.connectLock = false

      try {
        if (this.stompClient.connected) this.stompClient.disconnect()
      } catch (err) {}
      try {
        if (this.stompClient.ws?.readyState < 2) this.stompClient.ws.close()
      } catch (err) {}

      this.$emit('updateClient')

      if (this.stompClient.activeClose) {
        this.$emit('onClose')
      }
    },

    onSuccessHandle() {
      this.clearTimer()

      if (this.destination) {
        //连接成功后订阅相关地址发送的消息
        this.stompClient.subscribe(
          this.realDestination,
          this.onNewMessageHandle
        )
      }

      this.$emit('updateClient')

      this.connectLock = false

      this.connectRound = 0

      this.$emit('onSuccess')
    },

    /**
     * @description: 收到新消息向外抛出
     */
    onNewMessageHandle(response) {
      this.$emit('onNewMessage', response)
    },

    onErrorHandle(e) {
      this.clearTimer()

      this.$emit('updateClient')

      this.connectLock = false

      //主动关闭时不再触发重连
      if (this.stompClient.activeClose) {
        return
      }

      this.$emit('onError', e)

      this.reconnect()
    },

    emitErrorMsg(e) {
      this.$emit('onError', e)
    },

    /**
     * @description: 主动关闭连接，只有activeClose = true时不会触发继续重连
     */
    activeCloseMethod() {
      this.stompClient.activeClose = true
      this.disconnect()
    },

    /**
     * @description: 监听标签页是否在前台显示，防止链接处在后台过久断连
     */
    onVisibilityChange() {
      this.visibilityState = document.visibilityState

      if (document.visibilityState != 'hidden') {
        if (this.connectLock) return

        this.connectRound = 0
        this.reconnect()
      }
    }
  }
}
</script>

